/*******************************************************************************

  Pilot Intelligence Library
    http://www.pilotintelligence.com/

  ----------------------------------------------------------------------------

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.

*******************************************************************************/


#ifndef __OSAPP_POCO_H__
#define __OSAPP_POCO_H__


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <string.h>

#include <string>

// poco
#include <Poco/Thread.h>
#include <Poco/Runnable.h>
#include <Poco/Mutex.h>
#include <Poco/RWLock.h>
#include <Poco/Semaphore.h>
#include <Poco/Timestamp.h>
#include <Poco/Process.h>


namespace pi {


////////////////////////////////////////////////////////////////////////////////
/// Thread
////////////////////////////////////////////////////////////////////////////////

/** The Thread class encapsulates a thread of execution.  It is implemented with POSIX threads.
    Code that uses this class should link with libpthread and librt (for nanosleep).
*/
class Runnable
{
public:
    //! Perform the function of this object.
    virtual void threadFunc() = 0;

    Runnable() {}
    virtual ~Runnable() {}
};


class Thread : public Runnable
{
public:
    /// Thread priorities.
    enum Priority {
        PRIO_LOWEST  = Poco::Thread::PRIO_LOWEST,       /// The lowest thread priority.
        PRIO_LOW     = Poco::Thread::PRIO_LOW,          /// A lower than normal thread priority.
        PRIO_NORMAL  = Poco::Thread::PRIO_NORMAL,       /// The normal thread priority.
        PRIO_HIGH    = Poco::Thread::PRIO_HIGH,         /// A higher than normal thread priority.
        PRIO_HIGHEST = Poco::Thread::PRIO_HIGHEST       /// The highest thread priority.
    };

public:
    //! Construct a thread.  If runnable != 0, use that runnable, else use our own "run" method.
    Thread();

    //! This does not destroy the object until the thread has been terminated.
    virtual ~Thread() {
        if( isRunning() ) {
            stop();
            //join();
        }
    }

    //! Override this method to do whatever it is the thread should do.
    virtual void threadFunc() {
        printf("Please inheriet this function to use the thread!\n");
    }


    //! Start execution of "threadFunc" method in separate thread.
    void start(Runnable* runnable=0) {
        myRunnable = runnable ? runnable : this;
        myStopFlag = false;

        ourCount++;

        myThread.start(_threadProc, (void*)this);
    }

    //! Tell the thread to stop.
    /** This doesn't make the thread actually stop, it just causes shouldStop() to return true. */
    virtual void stop(void) {
        myStopFlag = true;
    }

    //! Returns true if the stop() method been called, false otherwise.
    inline bool shouldStop(void) const {
        return myStopFlag;
    }

    //! Returns true if the thread is still running.
    inline bool isRunning(void) const {
        return myRunningFlag;
    }

    //! This blocks until the thread has actually terminated.
    /** If the thread is infinite looping, this will block forever! */
    inline bool join(long milliseconds = 0) {
        bool ret = false;

        if( hasJoined ) return ret;

        if( milliseconds == 0 ) {
            myThread.join();
            ret = true;
        } else {
            ret = myThread.tryJoin(milliseconds);
        }

        hasJoined = true;
        return ret;
    }

    /// Waits for at most the given interval for the thread
    /// to complete. Returns true if the thread has finished,
    /// false otherwise.
    inline bool tryJoin(long milliseconds) {
        if( hasJoined ) return false;

        bool ret = myThread.tryJoin(milliseconds);

        hasJoined = true;
        return ret;
    }



    /// Returns the unique thread ID of the thread.
    int id(void) const {
        return myThread.id();
    }

    /// Returns the native thread ID of the thread.
    inline uint64_t tid(void) const {
        return (uint64_t) myThread.tid();
    }


    /// Sets the thread's stack size in bytes.
    /// Setting the stack size to 0 will use the default stack size.
    /// Typically, the real stack size is rounded up to the nearest
    /// page size multiple.
    inline void setStackSize(int size) {
        myThread.setStackSize(size);
    }

    /// Returns the thread's stack size in bytes.
    /// If the default stack size is used, 0 is returned.
    ///
    inline int getStackSize(void) const {
        return myThread.getStackSize();
    }


    ///
    /// \brief set thread's name
    ///
    /// \param name         - thread's name
    ///
    inline void setName(const std::string& name) {
        myName = name;
    }

    ///
    /// \brief get thread's name
    ///
    /// \return
    ///     thread's name
    ///
    inline std::string getName(void) {
        return myName;
    }


    /// Sets the thread's priority.
    ///
    /// Some platform only allow changing a thread's priority
    /// if the process has certain privileges.
    inline void setPriority(Priority prio) {
        myThread.setPriority((Poco::Thread::Priority) prio);
    }

    /// Returns the thread's priority.
    inline Priority getPriority() const {
        return (Priority) myThread.getPriority();
    }

    /// Sets the thread's priority, using an operating system specific
    /// priority value. Use getMinOSPriority() and getMaxOSPriority() to
    /// obtain mininum and maximum priority values.
    inline void setOSPriority(int prio) {
        myThread.setOSPriority(prio);
    }

    /// Returns the thread's priority, expressed as an operating system
    /// specific priority value.
    inline int getOSPriority(void) const {
        return myThread.getOSPriority();
    }

    /// Returns the mininum operating system-specific priority value,
    /// which can be passed to setOSPriority().
    static int getMinOSPriority(void);

    /// Returns the maximum operating system-specific priority value,
    /// which can be passed to setOSPriority().
    static int getMaxOSPriority(void);



    /// Returns the Thread object for the currently active thread.
    /// If the current thread is the main thread, 0 is returned.
    static Thread* current(void);

    /// Returns the native thread ID for the current thread.
    static uint64_t currentTid(void);


    //! Returns how many threads are actually running, not including the main thread.
    static unsigned int count(void);

    //! Tell the current thread to sleep for milli milliseconds
    static void sleep(unsigned int milli);

    //! Tell the current thread to yield the processor.
    static void yield(void);


protected:
    static void     _threadProc(void* param);

    static bool     init(void);
    static bool     ourInitializedFlag;

    static int64_t  ourCount;

    Poco::Thread    myThread;
    Runnable        *myRunnable;
    bool            myRunningFlag;
    bool            myStopFlag;
    int             hasJoined;

    std::string     myName;
};



////////////////////////////////////////////////////////////////////////////////
/// Mutex
////////////////////////////////////////////////////////////////////////////////

class Mutex
{
public:
    Mutex() {
    }

    ~Mutex() {
    }

    inline int lock() {
        try {
            m_mutex.lock();
        } catch(...) {
            return -1;
        }

        return 0;
    }

    inline int lock(unsigned int milli) {
        try {
            m_mutex.lock(milli);
        } catch(...) {
            return -1;
        }

        return 0;
    }

    inline int unlock() {
        try {
            m_mutex.unlock();
        } catch(...) {
            return -1;
        }

        return 0;
    }

    inline int tryLock() {
        try {
            return m_mutex.tryLock();
        } catch(...) {
            return -1;
        }

        return 0;
    }

private:
    Poco::Mutex m_mutex;
};


class ScopedMutex
{
public:
    ScopedMutex(Mutex& m_) : m(&m_) {
        m->lock();
    }

    ~ScopedMutex() {
        m->unlock();
    }

private:
    Mutex* m;
};


class MutexRW
{
public:
    MutexRW() {
    }

    ~MutexRW() {
    }

    inline int readLock() {
        try{
            m_mutex.readLock();
        } catch(...) {
            return -1;
        }

        return 0;
    }

    inline int tryReadLock() {
        if ( true == m_mutex.tryReadLock())
            return 0;

        return -1;
    }

    inline int writeLock() {
        try{
            m_mutex.writeLock();
        }
        catch(...) {
            return -1;
        }

        return 0;
    }

    inline int tryWriteLock() {
        if(true == m_mutex.tryWriteLock())
            return 0;

        return -1;
    }

    inline int unlock() {
        try{
            m_mutex.unlock();
        } catch(...) {
            return -1;
        }

        return 0;
    }

private:
    Poco::RWLock    m_mutex;
};


class ReadMutex
{
public:
    ReadMutex(MutexRW& m_) : m(&m_) {
        m->readLock();
    }

    ~ReadMutex() {
        m->unlock();
    }

private:
    MutexRW* m;
};


class WriteMutex
{
public:
    WriteMutex(MutexRW& m_) : m(&m_) {
        m->writeLock();
    }

    ~WriteMutex() {
        m->unlock();
    }

private:
    MutexRW* m;
};



////////////////////////////////////////////////////////////////////////////////
/// Semphore
////////////////////////////////////////////////////////////////////////////////

class Semaphore
{
public:
    Semaphore(int n=1) : m_sem(n) {
    }

    Semaphore(int n, int max) : m_sem(n, max) {

    }

    virtual ~Semaphore() {

    }

    /// Increments the semaphore's value by one and thus signals the semaphore.
    /// Another thread waiting for the semaphore will be able to continue.
    inline void set(void) {
        m_sem.set();
    }

    /// Waits for the semaphore to become signalled. To become signalled,
    /// a semaphore's value must be greater than zero. Decrements the semaphore's value by one.
    inline void wait(void) {
        m_sem.wait();
    }

    /// Waits for the semaphore to become signalled. To become signalled,
    /// a semaphore's value must be greater than zero. Returns true if the
    /// semaphore became signalled within the specified time interval, false otherwise.
    /// Decrements the semaphore's value by one if successful.
    inline bool tryWait(int32_t mills) {
        return m_sem.tryWait(mills);
    }

protected:
    Poco::Semaphore     m_sem;
};


////////////////////////////////////////////////////////////////////////////////
/// TimerTask
////////////////////////////////////////////////////////////////////////////////

typedef void (*OSA_TIMER_FUNC)(void *data);


class TimerTask : public Thread
{
public:
    enum Mode {
        TIMER_MODE_ONCE,
        TIMER_MODE_PERIODIC
    };

public:
    TimerTask() {
        m_timerMode = TIMER_MODE_PERIODIC;

        m_startInterval = 0;
        m_periodicInterval = 100;

        m_sleepTime = 10;
    }

    ///
    /// \brief Timer constructor
    ///
    /// \param periodicInterval     - periodic time interval (ms)
    /// \param startInterval        - start interval (ms)
    ///
    TimerTask(int64_t periodicInterval, int64_t startInterval = 0) {
        if( periodicInterval == 0 ) {
            // if periodic interval not set then to large number
            m_periodicInterval = 9999999999;
            m_timerMode = TIMER_MODE_ONCE;
        } else {
            // periodic timer
            m_periodicInterval = periodicInterval;
            m_timerMode = TIMER_MODE_PERIODIC;
        }

        m_startInterval = startInterval;
        m_sleepTime = 10;
    }

    virtual ~TimerTask() {
        stop();
        join();
    }

    ///
    /// \brief timer's thread function
    ///
    virtual void threadFunc() {
        sleep(m_startInterval);

        if( m_timerMode == TIMER_MODE_PERIODIC ) {
            Poco::Timestamp::TimeVal t1, t2, dt;
            Poco::Timestamp ts;

            while ( !shouldStop() ) {
                // get beginning time
                ts.update(); t1 = ts.epochMicroseconds();

                // call timer function
                if( m_timerFunc ) m_timerFunc(m_funcArg);
                else              timerFunc(this);

                // get running time
                ts.update(); t2 = ts.epochMicroseconds();
                dt = (t2 - t1) / 1000;

                // sleep for next loop
                if( dt < m_periodicInterval ) {
                    dt = m_periodicInterval - dt;

                    while ( dt > 0 ) {
                        if( dt > m_sleepTime )
                            Thread::sleep(m_sleepTime);
                        else
                            Thread::sleep(dt);

                        dt -= m_sleepTime;

                        if( shouldStop() ) return;
                    }
                }
            }
        }
    }

    ///
    /// \brief timer function
    ///
    /// \param data         - function argument
    ///
    virtual void timerFunc(void *data) {
        printf("Please inhieret the timerFunc!\n");
    }

    ///
    /// \brief start timer, which call timerFunc periodically
    ///
    virtual void start(void) {
        m_timerFunc = NULL;
        m_funcArg = NULL;

        Thread::start();
    }

    ///
    /// \brief start timer with given timer function with type of OSA_TIMER_FUNC
    ///
    /// \param func         - function handle
    /// \param arg          - timer argument
    ///
    virtual void start(OSA_TIMER_FUNC func, void *arg) {
        m_timerFunc = func;
        m_funcArg = arg;

        Thread::start();
    }

    ///
    /// \brief stop timer
    ///
    virtual void stop(void) {
        myStopFlag = true;
    }


    inline void setStartInterval(int64_t startInterval) {
        m_startInterval = startInterval;
    }

    inline void setPeriodicInterval(int64_t periodicInterval) {
        m_periodicInterval = periodicInterval;
    }

    inline int64_t getStartInterval(void) const {
        return m_startInterval;
    }

    inline int64_t getPeriodicInterval(void) const {
        return m_periodicInterval;
    }


protected:
    OSA_TIMER_FUNC  m_timerFunc;                        ///< timer function pointer
    void            *m_funcArg;                         ///< function arguments
    Mode            m_timerMode;                        ///< timer mode
    int             m_sleepTime;                        ///< sleep time

    int64_t         m_startInterval;                    ///< start the first event time (ms)
    int64_t         m_periodicInterval;                 ///< periodic time interval (ms)

    int             m_isRunning;
};


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


///
/// \brief Get current process's ID number
///
/// \return process ID number
///
uint64_t osa_getPID(void);

///
/// \brief Get current thread's ID number
///
/// \return thread ID number
///
uint64_t osa_getTID(void);

///
/// \brief return system status
///
/// \return
///     1           - running
///     0           - stopped
///
int isRunning(void);

///
/// \brief set system status
///
/// \param s        - status of system
///                     1 - run
///                     0 - stop
///
/// \return
///
int setStop(int s=0);


} // end of namespace pi

#endif // end of __OSAPP_POCO_H__
